package net.yxsoft.kit;

import net.yxsoft.bean.VideoSign;
import rapid.util.DateKit;

import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.zip.Deflater;

public class WebRTCSigApi {

  private int mSdkAppid = 0;
  private PrivateKey mPrivateKey = null;
  private PublicKey mPublicKey = null;

  public static int sdkappid = 1400197814;

  private static String privateKey = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg35LnqgMwRTsxgSb5F27fsSOr+ZOOcIGE0DtQnm1RPhOhRANCAAQlXxyF2XCb2fCcd1V6SswzGecl7p4E6iLuckhzXAdm4cmEELs9POCF04KR+TWOtIMuyOTQAvuaM1ek/xOUVgCV";
  private static String publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJV8chdlwm9nwnHdVekrMMxnnJe6eBOoi7nJIc1wHZuHJhBC7PTzghdOCkfk1jrSDLsjk0AL7mjNXpP8TlFYAlQ==";

  public WebRTCSigApi(){

    setSdkAppid(sdkappid);

    setPrivateKey(privateKey);
    setPublicKey(publicKey);
  }
  /**
   * 设置sdkappid
   * @param sdkappid
   */
  public void setSdkAppid(int sdkappid) {
    this.mSdkAppid = sdkappid;
  }

  /**
   * 设置私钥 如果要生成userSig和privateMapKey则需要私钥
   * @param privateKey 私钥文件内容
   */
  public void setPrivateKey(String privateKey) {
    String privateKeyPEM = privateKey.replace("-----BEGIN PRIVATE KEY-----", "")
        .replace("-----END PRIVATE KEY-----", "")
        .replace("\n", "");
    byte[] encodedKey = Base64.getDecoder().decode(privateKeyPEM.getBytes());
    try {
      PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
      KeyFactory keyFactory = KeyFactory.getInstance("EC");
      this.mPrivateKey = keyFactory.generatePrivate(keySpec);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 设置公钥 如果要验证userSig和privateMapKey则需要公钥
   * @param publicKey 公钥文件内容
   */
  public void setPublicKey(String publicKey) {
    String publicKeyPEM = publicKey.replace("-----BEGIN PUBLIC KEY-----", "")
        .replace("-----END PUBLIC KEY-----", "")
        .replace("\n", "");
    byte[] encodedKey = Base64.getDecoder().decode(publicKeyPEM.getBytes());

    try {
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
      KeyFactory keyFactory = KeyFactory.getInstance("EC");
      this.mPublicKey = keyFactory.generatePublic(keySpec);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * ECDSA-SHA256签名
   * @param data 需要签名的数据
   * @return 签名
   */
  public byte[] sign(byte[] data) {
    try {
      Signature signer = Signature.getInstance("SHA256withECDSA");
      signer.initSign(this.mPrivateKey);
      signer.update(data);
      return signer.sign();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 验证ECDSA-SHA256签名
   * @param data 需要验证的数据原文
   * @param sig 需要验证的签名
   * @return true:验证成功 false:验证失败
   */
  public boolean verify(byte[] data, byte[] sig) {
    try {
      Signature signer = Signature.getInstance("SHA256withECDSA");
      signer.initVerify(this.mPublicKey);
      signer.update(data);
      return signer.verify(sig);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }

  /**
   * 用于url的base64encode
   * '+' => '*', '/' => '-', '=' => '_'
   * @param data 需要编码的数据
   * @return 编码后的base64数据
   */
  private byte[] base64UrlEncode(byte[] data) {
    byte[] encode = Base64.getEncoder().encode(data);
    for (int i = 0; i < encode.length; i++) {
      if (encode[i] == '+') {
        encode[i] = '*';
      } else if (encode[i] == '/') {
        encode[i] = '-';
      } else if (encode[i] == '=') {
        encode[i] = '_';
      }
    }
    return encode;
  }

  /**
   * 用于url的base64decode
   * '*' => '+', '-' => '/', '_' => '='
   * @param data 需要解码的数据
   * @return 解码后的数据
   */
  private byte[] base64UrlDecode(byte[] data) {
    byte[] encode = Arrays.copyOf(data, data.length);
    for (int i = 0; i < encode.length; i++) {
      if (encode[i] == '*') {
        encode[i] = '+';
      } else if (encode[i] == '-') {
        encode[i] = '/';
      } else if (encode[i] == '_') {
        encode[i] = '=';
      }
    }
    return encode;
  }

  /**
   * 生成userSig
   * @param userid 用户名
   * @param expire userSig有效期，出于安全考虑建议为300秒，您可以根据您的业务场景设置其他值。
   * @return 生成的userSig
   */
  public String genUserSig(String userid, int expire) {
    String time = String.valueOf(System.currentTimeMillis()/1000);
    String serialString =
        "TLS.appid_at_3rd:" + 0 + "\n" +
            "TLS.account_type:" + 0 + "\n" +
            "TLS.identifier:" + userid + "\n" +
            "TLS.sdk_appid:" + this.mSdkAppid + "\n" +
            "TLS.time:" + time + "\n" +
            "TLS.expire_after:" + expire +"\n";

    byte[] signBytes = sign(serialString.getBytes(Charset.forName("UTF-8")));
    String sig = Base64.getEncoder().encodeToString(signBytes);

    String jsonString = "{"
        + "\"TLS.account_type\":\"" + 0 +"\","
        +"\"TLS.identifier\":\"" + userid +"\","
        +"\"TLS.appid_at_3rd\":\"" + 0 +"\","
        +"\"TLS.sdk_appid\":\"" + this.mSdkAppid +"\","
        +"\"TLS.expire_after\":\"" + expire +"\","
        +"\"TLS.sig\":\"" + sig +"\","
        +"\"TLS.time\":\"" + time +"\","
        +"\"TLS.version\": \"201512300000\""
        +"}";

    //compression
    Deflater compresser = new Deflater();
    compresser.setInput(jsonString.getBytes(Charset.forName("UTF-8")));

    compresser.finish();
    byte [] compressBytes = new byte [512];
    int compressBytesLength = compresser.deflate(compressBytes);
    compresser.end();
    String userSig = new String(base64UrlEncode(Arrays.copyOfRange(compressBytes, 0, compressBytesLength)));

    return userSig;
  }

  /**
   * 生成privateMapKey
   * @param userid 用户名
   * @param roomid 房间号
   * @param expire privateMapKey有效期，出于安全考虑建议为300秒，您可以根据您的业务场景设置其他值。
   * @return 生成的privateMapKey
   */
  public String genPrivateMapKey(String userid, int roomid, int expire) {
    String time = String.valueOf(System.currentTimeMillis()/1000);

    //视频校验位需要用到的字段
        /*
         cVer    unsigned char/1 版本号，填0
         wAccountLen unsigned short /2   第三方自己的帐号长度
         buffAccount wAccountLen 第三方自己的帐号字符
         dwSdkAppid  unsigned int/4  sdkappid
         dwRoomId    unsigned int/4  群组号码
         dwExpTime   unsigned int/4  过期时间 （当前时间 + 有效期（单位：秒，建议300秒））
         dwPrivilegeMap  unsigned int/4  权限位
         dwAccountType   unsigned int/4  第三方帐号类型
         */
    int accountLength = userid.length();
    int offset = 0;
    byte[] bytes = new byte[1+2+accountLength+4+4+4+4+4];

    //cVer
    bytes[offset++] = 0;

    //wAccountLen
    bytes[offset++] = (byte)((accountLength & 0xFF00) >> 8);
    bytes[offset++] = (byte)(accountLength & 0x00FF);

    //buffAccount
    for (; offset < 3 + accountLength; ++offset) {
      bytes[offset] = (byte)userid.charAt(offset - 3);
    }

    //dwSdkAppid
    bytes[offset++] = (byte)((this.mSdkAppid & 0xFF000000) >> 24);
    bytes[offset++] = (byte)((this.mSdkAppid & 0x00FF0000) >> 16);
    bytes[offset++] = (byte)((this.mSdkAppid & 0x0000FF00) >> 8);
    bytes[offset++] = (byte)(this.mSdkAppid & 0x000000FF);

    //dwAuthId
    long nRoomId = Long.valueOf(roomid);
    bytes[offset++] = (byte)((nRoomId & 0xFF000000) >> 24);
    bytes[offset++] = (byte)((nRoomId & 0x00FF0000) >> 16);
    bytes[offset++] = (byte)((nRoomId & 0x0000FF00) >> 8);
    bytes[offset++] = (byte)(nRoomId & 0x000000FF);

    //dwExpTime
    long expiredTime = Long.valueOf(time) + expire;
    bytes[offset++] = (byte)((expiredTime & 0xFF000000) >> 24);
    bytes[offset++] = (byte)((expiredTime & 0x00FF0000) >> 16);
    bytes[offset++] = (byte)((expiredTime & 0x0000FF00) >> 8);
    bytes[offset++] = (byte)(expiredTime & 0x000000FF);

    //dwPrivilegeMap
    bytes[offset++] = (byte)((255 & 0xFF000000) >> 24);
    bytes[offset++] = (byte)((255 & 0x00FF0000) >> 16);
    bytes[offset++] = (byte)((255 & 0x0000FF00) >> 8);
    bytes[offset++] = (byte)(255 & 0x000000FF);

    //dwAccountType
    bytes[offset++] = (byte)((0 & 0xFF000000) >> 24);
    bytes[offset++] = (byte)((0 & 0x00FF0000) >> 16);
    bytes[offset++] = (byte)((0 & 0x0000FF00) >> 8);
    bytes[offset++] = (byte)(0 & 0x000000FF);

    String userbuf = Base64.getEncoder().encodeToString(bytes);

    String serialString =
        "TLS.appid_at_3rd:" + 0 + "\n" +
            "TLS.account_type:" + 0 + "\n" +
            "TLS.identifier:" + userid + "\n" +
            "TLS.sdk_appid:" + this.mSdkAppid + "\n" +
            "TLS.time:" + time + "\n" +
            "TLS.expire_after:" + expire +"\n" +
            "TLS.userbuf:" + userbuf + "\n";

    byte[] signBytes = sign(serialString.getBytes(Charset.forName("UTF-8")));
    String sig = Base64.getEncoder().encodeToString(signBytes);

    String jsonString = "{"
        +"\"TLS.appid_at_3rd\":\"" + 0 +"\","
        +"\"TLS.account_type\":\"" + 0 +"\","
        +"\"TLS.identifier\":\"" + userid +"\","
        +"\"TLS.sdk_appid\":\"" + this.mSdkAppid +"\","
        +"\"TLS.expire_after\":\"" + expire +"\","
        +"\"TLS.sig\":\"" + sig +"\","
        +"\"TLS.time\":\"" + time +"\","
        +"\"TLS.userbuf\":\"" + userbuf +"\","
        +"\"TLS.version\": \"201512300000\""
        +"}";

    //compression
    Deflater compresser = new Deflater();
    compresser.setInput(jsonString.getBytes(Charset.forName("UTF-8")));

    compresser.finish();
    byte [] compressBytes = new byte [512];
    int compressBytesLength = compresser.deflate(compressBytes);
    compresser.end();
    String privateMapKey = new String(base64UrlEncode(Arrays.copyOfRange(compressBytes, 0, compressBytesLength)));

    return privateMapKey;
  }

  public VideoSign getSign(String userId, int roomId){

    VideoSign sign = new VideoSign();
    /**
     * 有效期10分钟
     */
    sign.setUserSign(genUserSig(userId, 60 * 10));
    sign.setPrivateMapKey(genPrivateMapKey(userId, roomId, 60 * 10));

    return sign;
  }

  public static void main(String[] args) {

    WebRTCSigApi api = new WebRTCSigApi();
    VideoSign sign = api.getSign("10012",201038);

    System.out.println("userSig:\n" + sign.getUserSign());
    System.out.println("privateMapKey:\n" + sign.getPrivateMapKey());

    System.out.println(DateKit.getDateByTime() / 1000);
  }
}
